﻿using System;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk;

/* Note: I have not yet checked what happens when the application (or the project) 
 * is closed (Project.Disconnect) and when there are certain requests in the queue. 
 * Normally, there should be a Dispose method and all outstanding requests should 
 * be cancelled by calling their RequestCompletedHandler with an appropriate error message.
 */

namespace ScriptEase
{
    /// <summary>
    /// Dispatches CRM requests.
    /// </summary>
    /// <remarks>
    /// If you send two Update/Publish requests at the same time to CRM, for example, when 
    /// all files are saved within Visual Studio, CRM will throw generic SQL errors.
    /// For that reason, <see cref="OrganizationRequest"/> are queued and sent one 
    /// after the other.
    /// </remarks>
    internal class CrmRequestDispatcher
    {
        private readonly IOrganizationService _orgService;
        private readonly List<DispatchRequest> _dispatchQueue = new List<DispatchRequest>();
        private readonly object _listock = new object();



        public CrmRequestDispatcher(IOrganizationService orgService)
        {
            _orgService = orgService;
        }



        /// <summary>
        /// NewItem the request to the dispatch queue.
        /// </summary>
        /// <remarks>
        /// If the request is the first request in the queue, 
        /// the request will be sent immediately to CRM.
        /// </remarks>
        /// <param name="request">The CRM request.</param>
        /// <param name="requestCompleted">
        /// A handler that is called when this request is completed.
        /// </param>
        /// <param name="addData">
        /// User specific additional data that is passed 
        /// back to the client when the request is completed.
        /// </param>
        public void AddRequest(OrganizationRequest request,
                               OrganizationServiceExtension.RequestCompletedHandler requestCompleted,
                               object addData)
        {
            lock (_listock)
            {
                _dispatchQueue.Add(new DispatchRequest(request, requestCompleted, addData));
                Trace.Default.Debug("{0} requests in the dispatch queue!", _dispatchQueue.Count);
                if (_dispatchQueue.Count == 1)
                    _orgService.BeginRequest(request, requestCompletedHandler, addData);
            }
        }



        /// <summary>
        /// The local handler that is called every time a request is finsihed.
        /// </summary>
        /// <remarks>
        /// When a request is finished the user's completed handler is called (see <see cref="AddRequest"/>),
        /// then the requets is removed from the dispatch queue and the next request is started (if any).
        /// </remarks>
        /// <param name="response">The response.</param>
        /// <param name="addData">The add data.</param>
        /// <param name="result">The result.</param>
        private void requestCompletedHandler(OrganizationResponse response, object addData, Exception result)
        {
            lock (_listock)
            {
                Trace.Default.Debug("Response received, {0} items left", _dispatchQueue.Count);

                // get the item for which we got the response 
                // savely send the response to the client 
                // and then remove the item from the list

                DispatchRequest dispatchRequest = _dispatchQueue[0];
                try
                {
                    dispatchRequest.RequestCompleted(response, addData, result);
                }
                catch (Exception e)
                {
                    Trace.Default.LogException(e);
                }
                _dispatchQueue.Remove(dispatchRequest);


                //
                // if items left, send next request

                if (_dispatchQueue.Count > 0)
                {
                    _orgService.BeginRequest(_dispatchQueue[0].Request, requestCompletedHandler, _dispatchQueue[0].AddData);
                }
            }
        }



        /// <summary>
        /// Local class to manage the dispatched (and asynchronous) requests.
        /// </summary>
        private class DispatchRequest
        {
            public DispatchRequest(OrganizationRequest request, OrganizationServiceExtension.RequestCompletedHandler requestCompleted, object addData)
            {
                Request = request;
                RequestCompleted = requestCompleted;
                AddData = addData;
            }



            public OrganizationRequest Request { get; private set; }
            public OrganizationServiceExtension.RequestCompletedHandler RequestCompleted { get; private set; }
            public object AddData { get; private set; }
        }
    }
}